#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/time.h>
#include <errno.h>

//#define DEBUG
#define PRINT_RESULT
#define PRINT_T1
#define PRINT_T2
#define PRINT_T3

/*
 * @brief imprime uma mensagem na tela
 * @param mensagem a ser impressa na tela
 */
void msg(char* message){
#ifdef DEBUG
	printf("%s", message);
	printf("\n");
#endif
}

/*
 * @brief compara dois elementos de um vetor
 * @param elementos 'a' e 'b' de um vetor
 * @return um valor positivo se a>b, um valor negativo se a<b e 0 se a==b
 */
int compare(const void* a, const void* b){
  return (*(int*)a - *(int*)b);
}

/*
 * @brief funcao que captura o tempo
 * @return um double com o tempo
 */
double getTempo() {
    struct timeval tv;
    gettimeofday(&tv,0);
    return tv.tv_sec + tv.tv_usec/1e6;
}

/*
 * @brief intercala dois vetores de tamanhos l1 e l2 ordenados crescentemente em um terceiro vetor ordenado, de tamanho l1+l2
 * @param ponteiro para vetor 'v1' de comprimento 'l1', para vetor 'v2' de comprimento 'l2' e para 'vout' que deve ter tamanho 'l1+l2'
 */
void merge(int* v1, int l1, int* v2, int l2, int* vout){
	int i1 = 0;
	int i2 = 0;
	int iout = 0;
	while(i1 < l1 && i2 < l2){
	  if(v1[i1] < v2[i2]){
		vout[iout] = v1[i1++];
	  }else{
		vout[iout] = v2[i2++];
	  }
	  iout++;
	}

	while(i1 < l1){
	  vout[iout] = v1[i1];
	  iout++;i1++;
	}
	while(i2 < l2){
	  vout[iout] = v2[i2];
	  iout++;i2++;
	}
}

void printArray(int* array, int len){
	printf("%d", array[0]);
	for(int i = 1; i < len; i++){
	  printf(", %d", array[i]);
	}
	printf("\n");
}

#define LEN 125
#define PARTES 8

/*
 * @brief funcao que executa um cliente no noh do cluster
 * @param 'host' eh o nome do servidor com quem ele vai ser conectar, 'porta' eh a porta de comunicacao, 'v' eh o vetor que ele envia e 'buffer' eh o             vetor em que ele recebe o resultado e 'tam' eh o tamanho dele em cada nivel da arquitetura
 */
void rotinaCliente(char* host, int porta, int* v, int* buffer, int tam){
	msg("inicio rotina cliente");
    int sockfd, portno, n;					/* descritor de arquivo, numero da porta e retorno das funcoes read/write */
    struct sockaddr_in serv_addr;				/* estrutura do servidor */
    struct hostent *server;

    portno = porta;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);			/* criando um novo socket do tipo stream e protocolo TCP */

    if (sockfd < 0)
        fprintf(stderr, "ERRO AO ABRIR SOCKET\n");

    server = gethostbyname(host);

    if (server == NULL) {
        fprintf(stderr,"ERRO! SERVIDOR INEXISTENTE\n");
        exit(0);
    }

    bzero((char *) &serv_addr, sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;

    bcopy((char *)server->h_addr_list[0], (char *)&serv_addr.sin_addr.s_addr, server->h_length);

    serv_addr.sin_port = htons(portno);				/* host byte order -> network byte order */

    if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0) 	/* conecta na internet */
        fprintf(stderr, "ERRO AO CONECTAR A INTERNET", strerror(errno));

    double t1 = getTempo();					/* captura do tempo antes do envio */
    n = write(sockfd,v,tam*sizeof(int));			/* escrita do vetor no socket */
    t1 = getTempo() - t1;					/* captura do tempo apos o envio */

    if (n < 0)
         fprintf(stderr, "ERRO AO ESCREVER NO SOCKET");

    //msg("vetor enviado");
    fprintf(stdout, "Vetor [%d] enviado ao servidor %s\n", tam, host);

#ifdef PRINT_T1    
printf("Tempo de envio: %.6f segundos\n", t1);
#endif

    bzero(buffer,PARTES*LEN*sizeof(int));			/* zerando o buffer */

    t1 = getTempo();						/* captura do tempo antes do recebimento */ 
    n = read(sockfd,buffer,PARTES*LEN*sizeof(int));		/* leitura do vetor no socket */
    t1 = getTempo() - t1;					/* captura do tempo apos o recebimento */

    fprintf(stdout, "Vetor [%d] recebido do servidor %s\n", PARTES*LEN, host);
    msg("resultado lido");

    if (n < 0)
         fprintf(stderr, "ERRO AO LER DO SOCKET\n");

#ifdef PRINT_RESULT
    printArray(buffer, n/sizeof(int));
#endif

    close(sockfd);

    return;
}

void rotinaServidor(int porta1, char* hostServ, int portaServ, int root){
	msg("inicio rotina servidor");
     int sockfd1, newsockfd1, newsockfd2;			/* descritores de arquivos */
     socklen_t clilen1, clilen2;				/* tamanho do endereco do cliente */
     int buffer1[PARTES*LEN], buffer2[PARTES*LEN], buffer3[PARTES*LEN];				/* buffer de leitura */
     struct sockaddr_in serv_addr1, cli_addr1, cli_addr2;	/* estrutura com endereco da internet servidor e cliente */
	 int n;

     sockfd1 = socket(AF_INET, SOCK_STREAM, 0);	/* criando um socket. Parametros: AF_INET = internet / SOCK_STREAM = tipo de socket (stream) / 0 = SO vai escolher o protocolo de comunicacao - TCP para socket stream */
     if (sockfd1 < 0)
        fprintf(stderr, "ERRO AO ABRIR SOCKET\n");

     bzero((char *) &serv_addr1, sizeof(serv_addr1));	/* inicializa vetor serv_addr com zeros */

     serv_addr1.sin_family = AF_INET;
     serv_addr1.sin_addr.s_addr = INADDR_ANY;		/* endereco IP */
     serv_addr1.sin_port = htons(porta1);		/* host byte order -> network byte order */

     if (bind(sockfd1, (struct sockaddr *) &serv_addr1,	/* sincroniza o servidor com um endereco e porta */
              sizeof(serv_addr1)) < 0)
              fprintf(stderr, "ERRO NA SINCRONIZACAO: %s\n", strerror(errno));

     listen(sockfd1,5);					/* 'escuta' conexoes de clientes - no maximo 5 em fila */

     clilen1 = sizeof(cli_addr1);

	 msg("aguardando conexao");
     newsockfd1 = accept(sockfd1, (struct sockaddr *) &cli_addr1, &clilen1);	/* nova conexao */

     if (newsockfd1 < 0)
          fprintf(stderr, "ERRO AO ACEITAR CONEXAO\n");
     msg("conexao estabelecida");

     bzero(buffer1,PARTES*LEN*sizeof(int));					/* inicializa o buffer */

    double t1 = getTempo();

     int l1 = read(newsockfd1,buffer1,PARTES*LEN*sizeof(int)) / sizeof(int);			/* le do cliente */
     if (n < 0) fprintf(stderr, "ERRO AO LER DO SOCKET\n");

    t1 = getTempo() - t1;
   // printf("\nTempo de recebimento do cliente: %.6f segundos\n", t1);
//Fim do recebimento cliente 1

     clilen2 = sizeof(cli_addr2);
     msg("aguardando segunda conexao");

     newsockfd2 = accept(sockfd1, (struct sockaddr *) &cli_addr2, &clilen2);	/* nova conexao */

     if (newsockfd2 < 0)
          fprintf(stderr, "ERRO AO ACEITAR CONEXAO\n");
	
     msg("segunda conexao estabelecida");

     bzero(buffer2,PARTES*LEN*sizeof(int));					/* inicializa o buffer */

     t1 = getTempo();

     int l2 = read(newsockfd2,buffer2,PARTES*LEN*sizeof(int)) / sizeof(int);			/* le do cliente */

     if (n < 0) fprintf(stderr, "ERRO AO LER DO SOCKET\n");
     t1 = getTempo() - t1;

     //printf("\nTempo de recebimento do cliente: %.6f segundos\n", t1);

//Fim do recebimento cliente 2

	  int* v3 = (int*)malloc((l1+l2)*sizeof(int));
	  int* bufferEnvio;
	  //ordenacao dos vetores
	  double t2 = getTempo();
	  merge(buffer1, l1, buffer2, l2, v3);
	  t2 = getTempo() - t2;

#ifdef PRINT_T2
	printf("Tempo de ordenacao do vetor [%d]: %.6f segundos\n", (l1+l2), t2);
#endif
	  msg("merge finalizado");
	  if(!root){

		//printf("hostserv: %s\tportaserv: %d\tl1+l2: %d\t", hostServ, portaServ, l1+l2);

	  	rotinaCliente(hostServ, portaServ, v3, buffer3, (l1+l2));

	  	bufferEnvio = buffer3;
	  }else{
		  bufferEnvio = v3;

#ifdef PRINT_RESULT		  
	printArray(v3, l1+l2);
#endif	  
	  }

//Inicio dos envios
    t1 = getTempo();

     n = write(newsockfd1, bufferEnvio, PARTES*LEN*sizeof(int));	/* escreve vetor para o cliente */
     if (n < 0) fprintf(stderr, "ERROR writing to socket");

	fprintf(stdout, "Vetor [%d] enviado ao cliente 1\n", PARTES*LEN);

     n = write(newsockfd2, bufferEnvio,PARTES*LEN*sizeof(int));		/* escreve vetor para o cliente */
     if (n < 0) fprintf(stderr, "ERROR writing to socket");

	fprintf(stdout, "Vetor [%d] enviado ao cliente 2\n", PARTES*LEN);

    t1 = getTempo() - t1;

#ifdef PRINT_T1   
printf("Tempo de envio aos clientes: %.6f segundos\n\n", t1);
#endif

     if (v3 != NULL) free(v3);
     close(newsockfd1);
     close(sockfd1);
     close(newsockfd2);

     return;
}

int main(int argc, char** argv){

	if(argc < 3) {
	  printf("Uso correto: \nprograma cli <host> <porta> OU\
	\nprograma serv <portaescuta> <host> <porta> OU\
	\nprograma root <portaescuta>\n");
	  exit(0);
	}
#ifdef DEBUG
	printf("tipo da execucao: %s\n", argv[1]);
#endif
	if(strcmp(argv[1], "cli") == 0) {
		
          msg("programa cliente");
	  int buffer[PARTES*LEN];
	  char* host = argv[2];
	  int porta;

	  sscanf(argv[3], "%d", &porta);

	  srand(time(NULL));
	  int v[LEN];
	  for(int i = 0; i < LEN; i++) {
		v[i] = rand()%10000;
	  }

	  double t_ord = getTempo();
	  qsort(v, LEN, sizeof(int), compare);
	  t_ord = getTempo() - t_ord;
	
	fprintf(stdout, "Tempo de ordenacao do vetor [%d]: %.6f segundos\n", LEN, t_ord);

	  double t0 = getTempo();
	  rotinaCliente(host, porta, v, buffer, LEN);
	  t0 = getTempo() - t0;

	fprintf(stdout, "Tempo total de operacao: %0.6f\n\n", t0);
	}

	if(strcmp(argv[1], "serv") == 0 || strcmp(argv[1], "root") == 0){
		msg("programa servidor");
	  char hostserv[128];
	  int porta1, portaserv = 0;
	  int root; //booleano
	  sscanf(argv[2], "%d", &porta1);

	  root = (strcmp(argv[1], "root") == 0);

	  if(root==0 && (argc == 5)){

	    strcpy(hostserv, argv[3]);
    	    sscanf(argv[4], "%d", &portaserv);

#ifdef DEBUG
printf ("hostserv: %s\tportaserv: %d\n", hostserv, portaserv);
#endif	
	  }

	  rotinaServidor(porta1, hostserv, portaserv, root);

	}

	msg("execucao completa");
	exit(0);

}



